// tspcluster - Traveling Salesman Problem
// Copyright (C) 2006  Frederik Carlier
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

using System;
using System.Globalization;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.Threading;
using System.IO;

namespace UGent.Tsp
{
    /// <summary>
    /// Represents a cluster of Markov Chains.
    /// </summary>
    public class MarkovCluster
    {
        // Check back every second;
        public const int Interval = 1000;

        public MarkovCluster(Map map, Type swapperType)
        {
            this.map = map;
            this.swapperType = swapperType;
            this.count = 1;
        }

        public MarkovCluster()
        {
            this.chains = new Collection<MarkovChain>();
            this.addresses = new Collection<string>();
        }

        private int count;
        private Type swapperType;
        private Collection<MarkovChain> chains;
        private Collection<string> addresses;
        private Map map;
        private Thread worker;
        private bool running;
        private long progress;
        private long maximumSteps = 0;
        private int resumeCount = 0;
        
        /// <summary>
        /// The number of steps in this cluster.
        /// </summary>
        public long Progress
        {
            get { return progress; }
        }

        /// <summary>
        /// Gets or sets the maximum number of steps in this cluster.
        /// The cluster will stop once the number of steps exceeds this value.
        /// If set to 0, the cluster will never stop.
        /// </summary>
        public long MaximumSteps
        {
            get { return maximumSteps; }
            set { maximumSteps = value; }
        }

        public void AddNode(string address, int port)
        {
            if (address == "(local)")
            {
                MarkovChain chain = new MarkovChain();
                chain.Reset();
                chain.SetMap(map);
                chain.SetSwapper(swapperType, count);
                lock(chains)
                {
                    chains.Add(chain);
                    chain.ChainId = chains.Count - 1;
                }
                addresses.Add("(local)");
            }
            else
            {
                ComputerInfo remoteComputerInfo = RemotingClient.GetRemoteComputerInfo(address, port);
                int numberOfProcessors = remoteComputerInfo.GetNumberOfProcessors();
                Console.WriteLine("This host has {0} processors.", numberOfProcessors);
                for (int i = 0; i < numberOfProcessors; i++)
                {
                    MarkovChain chain = RemotingClient.GetRemoteMarkovChain(address, port, i);
                    chain.Reset();
                    chain.SetMap(map);
                    chain.SetSwapper(swapperType, count);
                    lock(chains)
                    {
                        chains.Add(chain);
                        chain.ChainId = chains.Count - 1;
                    }
                    addresses.Add(string.Format("{0}:{1} ({2})", address, port, i + 1));
                }
            }
        }

        public void AddNode(string address)
        {
            AddNode(address, RemotingServer.DefaultPortNumber);
        }

        /// <summary>
        /// Starts the Markov Cluster.
        /// </summary>
        public void Start()
        {
            if (chains.Count == 0)
                throw new InvalidOperationException();

            running = true;
            progress = 0;
            resumeCount = 0;
            
            // Wait for all chains to be started before going into the
            // main thread
            foreach (MarkovChain chain in chains)
                chain.Start();

            worker = new Thread(new ThreadStart(Run));
            worker.Start();
        }
        
        /// <summary>
        /// Resumes the Markov Cluster.
        /// </summary>
        public void Resume()
        {
            if (chains.Count == 0)
                throw new InvalidOperationException();

            running = true;
            progress = 0;
            resumeCount += 1;
            Console.WriteLine("resumeCount: {0}", resumeCount);
            
            // Wait for all chains to be started before going into the
            // main thread
            foreach (MarkovChain chain in chains)
                chain.Resume();

            worker = new Thread(new ThreadStart(Run));
            worker.Start();
        }
        
        private void Run()
        {
            while (running)
            {
                Thread.Sleep(Interval);
                progress = 0;
                for (int i = 0; i < chains.Count; i++)
                {
                    MarkovChain chain = chains[i];
                    long chainProgress = chain.GetProgress();

                    Debug.WriteLine(string.Format("Progress of Chain {0}: {1}.", addresses[i], chainProgress));

                    progress += chainProgress;
                }

                if (maximumSteps > 0 && progress > (1+resumeCount)*maximumSteps)
                    running = false;

                Console.WriteLine(string.Format("Total progress: {0}. Maximum: {3}", progress, maximumSteps, resumeCount, (1+resumeCount)*MaximumSteps));
            }

            foreach (MarkovChain chain in chains)
                chain.Stop();
        }

        /// <summary>
        /// Stops the Markov Cluster.
        /// </summary>
        public void Stop()
        {
            Debug.WriteLine("Stopping all Markov chains. Please wait.");
            running = false;
        }

        /// <summary>
        /// Waits for all Markov Chains to stop.
        /// </summary>
        public void WaitForAll()
        {
            while (true)
            {
                int running = 0;

                foreach (MarkovChain chain in chains)
                {
                    if (chain.GetStatus() != MarkovChain.Status.Stopped)
                        running++;
                }

                if (running > 0)
                    Thread.Sleep(Interval);
                else
                    break;
            }
        }

        /// <summary>
        /// Estimates the autocorrelation time of this Markov Cluster.
        /// </summary>
        /// <returns>The estimated autocorrelation time of this Markov Cluster.</returns>
        public double EstimateAutocorrelationTime()
        {
            Results results = new Results();
            foreach (MarkovChain chain in chains)
                results.Add(chain.GetAutocorrelationTime());

            return results.GetAverage();
        }

        /// <summary>
        /// Gets the <see cref="Results"/> of this Markov Cluster.
        /// </summary>
        /// <returns>The <see cref="Results"/> of this Markov Cluster.</returns>
        public Results GetResults()
        {
            Results results = chains[0].GetResults();
            results.Transitions.Normalize();
            
            for(int i = 1; i < chains.Count; i++)
            {
                Results chainResults = chains[i].GetResults();
                chainResults.Transitions.Normalize();
                results.Merge(chainResults);
            }
            return results;
        }

        /// <summary>
        /// Resets the Markov Cluster.
        /// </summary>
        public void Reset()
        {
            foreach (MarkovChain chain in chains)
                chain.Reset();
        }

        /// <summary>
        /// Sets the <see cref="Map"/> to be used by the Markov Cluster.
        /// </summary>
        /// <param name="map">The <see cref="Map"/> to be used by the Markov Cluster.</param>
        public void SetMap(Map map)
        {
            foreach (MarkovChain chain in chains)
                chain.SetMap(map);
        }

        /// <summary>
        /// Sets the distribution to use when doing Metropolis-Hastings sampling.
        /// </summary>
        /// <param name="value">The distribution to use when doing Metropolis-Hastings sampling.</param>
        public void SetBeta(double beta)
        {
            Debug.WriteLine("Setting value of beta for all Markov Chains. Please wait.");

            foreach (MarkovChain chain in chains)
            {
                if (chain.GetStatus() != MarkovChain.Status.Stopped)
                    throw new InvalidOperationException();

                chain.SetBeta(beta);
            }
        }

        public void SetSwapper(Type type, int count)
        {
            foreach (MarkovChain chain in chains)
            {
                if (chain.GetStatus() != MarkovChain.Status.Stopped)
                    throw new InvalidOperationException();

                chain.SetSwapper(type, count);
            }
        }

        private int correlationCount;
        public int CorrelationCount { get { return correlationCount; } }
        
        public void SetCorrelationCount(int value)
        {
            this.correlationCount = value;
            
            foreach (MarkovChain chain in chains)
                chain.CorrelationCount = value;
        }

        private int histogramNumberOfBins;
        public int HistogramNumberOfBins { get { return histogramNumberOfBins; } }
        
        public void SetHistogramNumberOfBins(int value)
        {
            this.histogramNumberOfBins = value;
            
            foreach (MarkovChain chain in chains)
                chain.HistogramNumberOfBins = value;
        }

        private double histogramLower;
        public double HistogramLower { get { return histogramLower; } }
        
        public void SetHistogramLower(double value)
        {
            this.histogramLower = value;
            
            foreach (MarkovChain chain in chains)
                chain.HistogramLower = value;
        }

        private double histogramUpper;
        public double HistogramUpper { get { return histogramUpper; } }
        
        public void SetHistogramUpper(double value)
        {
            this.histogramUpper = value;
            
            foreach (MarkovChain chain in chains)
                chain.HistogramUpper = value;
        }

        private bool histogramLowerExtended;
        public bool HistogramLowerExtended { get { return histogramLowerExtended; } }
        
        public void SetHistogramLowerExtended(bool value)
        {
            this.histogramLowerExtended = value;
            
            foreach(MarkovChain chain in chains)
                chain.HistogramLowerExtended = value;
        }

        private bool histogramUpperExtended;
        public bool HistogramUpperExtended { get { return histogramUpperExtended; } }
        
        public void SetHistogramUpperExtended(bool value)
        {
            this.histogramUpperExtended = value;
            
            foreach (MarkovChain chain in chains)
                chain.HistogramUpperExtended = value;
        }

        public void SetUseUpperLimit(bool value)
        {
            foreach (MarkovChain chain in chains)
                chain.UseUpperLimit = value;
        }
        
        public void SetUpperLimit(double value)
        {
            foreach (MarkovChain chain in chains)
                chain.UpperLimit = value;
        }
        
        public void SetDistribution(Distribution value)
        {
            foreach (MarkovChain chain in chains)
                chain.SetDistribution(value);
        }

        public void SetTransitionMatrix(Transitions value)
        {
            foreach (MarkovChain chain in chains)
                chain.SetTransitionMatrix(value);
        }
        
        public void SetAlgorithmType(AlgorithmType value)
        {
            foreach (MarkovChain chain in chains)
                chain.AlgorithmType = value;
        }

        public void SetBiasForLowEnergy(bool value)
        {
            foreach(MarkovChain chain in chains)
                chain.BiasForLowEnergy = value;
        }

        public void SetWangLandauParameter(double value)
        {
            foreach(MarkovChain chain in chains)
                chain.WangLandauParameter = value;
        }
        
        public void ConfigureOnConsole()
        {
            Console.CancelKeyPress += (ConsoleCancelEventHandler)delegate
            {
                Console.WriteLine();
                Console.WriteLine("Stopping cluster. Please wait...");
                Stop();
                WaitForAll();
                Console.WriteLine("Done.");
            };
            
            if(File.Exists("servers"))
            {
                using(StreamReader reader = File.OpenText("servers"))
                {
                    while(reader.Peek() >= 0)
                    {
                        string line = reader.ReadLine();
                        AddNode(line);
                    }
                }

                return;
            }

            while (true)
            {
                Console.WriteLine("Please enter the name of the Markov node to connect to or ENTER to continue.");
                string value = Console.ReadLine();
                if (string.IsNullOrEmpty(value))
                    break;

                if (value.Contains(":"))
                {
                    string[] parts = value.Split(new char[] { ':' });
                    AddNode(parts[0], Int32.Parse(parts[1]));
                }
                else
                    AddNode(value);
            }
        }

        public void ConfigureSimulationOnConsole()
        {
            Console.WriteLine("Please enter the name of the map you want to use for the simulation.");
            Console.WriteLine("To create a random map, type (random)");
            
            string filename = Console.ReadLine();
            
            Map map;
            if(filename == "(random)")
                map = new Map();
            else
                map = new Map(filename);
            
            Console.WriteLine();
            double minimum = map.LowerBound;
            double maximum = map.UpperBound;
            
            Console.WriteLine("The route length is bounded by {0} and {1}", minimum, maximum);
            Console.WriteLine("What should the upper bound of the simulation be?");
            string line = Console.ReadLine();
            maximum = double.Parse(line, CultureInfo.InvariantCulture);
            
            Console.WriteLine("What resolution do you wish?");
            line = Console.ReadLine();
            int resolution = Int32.Parse(line, CultureInfo.InvariantCulture);
            
            Console.WriteLine("Simulation summary:");
            Console.WriteLine("Histogram minimum:    {0}", minimum);
            Console.WriteLine("Histogram maximum:    {0}", maximum);
            Console.WriteLine("Histogram resolution: {0}", resolution);
            
            SetMap(map);
            SetSwapper(Swappers.GetOptimalSwapper(), Swappers.GetCountForOptimalSwapper());
            SetCorrelationCount(0);
            SetHistogramLower(minimum);
            SetHistogramLowerExtended(false);
            SetHistogramUpper(maximum);
            SetHistogramUpperExtended(true);
            SetHistogramNumberOfBins(resolution);
            MaximumSteps = 500000;
        }
        
        public static void RunOnConsole()
        {
            MarkovCluster cluster = new MarkovCluster();
            cluster.ConfigureOnConsole();
            cluster.Start();

            Console.WriteLine("Press ENTER to quit.");
            Console.ReadLine();
            cluster.Stop();
        }
    }
}
